package com.example.androidaudiovideodemo.utils

import android.app.Application
import android.content.Context
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaExtractor
import android.media.MediaFormat
import android.util.Log
import com.blankj.utilcode.util.SDCardUtils
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.Exception
import java.nio.ByteBuffer
import java.util.*
import kotlin.concurrent.thread

/**
 *
 * author: renbing
 *
 * date: 2022/2/8
 *
 * des:
 */
class AudioCodecUtil {

    companion object{
        /**
         * 编码
         * 将pcm文件编码为aac文件
         */
        fun pcm2AAC(pcmPath: String,audioPath: String,encodeSuccess: () -> Unit,encodeFail: () -> Unit){
            thread {
                try {
                    val fis = FileInputStream(pcmPath)
                    //此处buffer大小需要和下面MediaFormat的KEY_MAX_INPUT_SIZE匹配  否则数组越界
                    //todo size设置太大编码后数据丢失90%  不清楚为啥
                    val buffer = ByteArray(10*1024)
                    var inputIndex: Int
                    var inputBuffer: ByteBuffer?
                    var outputIndex: Int
                    var outputBuffer: ByteBuffer?
                    var outBitSize: Int
                    var outPacketSize: Int

                    val encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,44100,2)
                    encodeFormat.run {
                        setInteger(MediaFormat.KEY_BIT_RATE,96000)
                        setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC)
                        setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,10*1024)
                    }
                    //创建编码器
                    val mediaEncode = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
                    //配置编码器
                    mediaEncode.configure(encodeFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
                    //开始
                    mediaEncode.start()
                    val encodeBufferInfo = MediaCodec.BufferInfo()

                    val fos = FileOutputStream(File(audioPath))
                    var isReadEnd = false
                    while (!isReadEnd){
                        //从输入流队列中取出数据并进行编码操作 传入的参数值表示等待多少毫秒  -1表示一直等待  0表示不等待  有可能丢帧 其余时间表示等待多久
                        inputIndex = mediaEncode.dequeueInputBuffer(-1)
                        //从input缓存区取出buffer
                        inputBuffer = mediaEncode.getInputBuffer(inputIndex)
                        inputBuffer?.clear()
                        val bytesRead = fis.read(buffer,0,inputBuffer?.capacity()!!)
                        if (bytesRead < inputBuffer.capacity()){
                            isReadEnd = true
                        }
                        inputBuffer.limit(bytesRead)
                        //将pcm数据填充到inputBuffer中
                        inputBuffer.put(buffer,0,bytesRead)
                        //将InputBuffer按编号放到Input缓冲区
                        mediaEncode.queueInputBuffer(inputIndex,0,bytesRead,0,if (!isReadEnd) 0 else MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        //从Output缓冲区获取已经解码好的OutputBuffer编号 传入BufferInfo记录数据size和数据flag等信息
                        outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo,-1)
                        while (outputIndex >= 0){
                            outBitSize = encodeBufferInfo.size
                            outPacketSize = outBitSize+7
                            //取出已经解码好的OutputBuffer
                            outputBuffer = mediaEncode.getOutputBuffer(outputIndex)
                            outputBuffer?.limit(encodeBufferInfo.offset+encodeBufferInfo.size)
                            val targetByte = ByteArray(outPacketSize)
                            //给每一帧添加adts头  因为每一帧都有adts头 所以aac文件可以从任何一帧开始解码
                            addADTSHead(targetByte,outPacketSize)
                            outputBuffer?.get(targetByte,7,outBitSize)
                            outputBuffer?.position(encodeBufferInfo.offset)
                            try {
                                fos.write(targetByte)
                            }catch (e: Exception){
                                e.printStackTrace()
                                encodeFail.invoke()
                            }
                            mediaEncode.releaseOutputBuffer(outputIndex,false)
                            outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo,0)
                        }
                    }
                    encodeSuccess.invoke()
                    mediaEncode.stop()
                    mediaEncode.release()
                    fos.close()
                }catch (e: Exception){
                    e.printStackTrace()
                    encodeFail.invoke()
                }
            }
        }

        /**
         * 添加ADTS头
         */
        private fun addADTSHead(packet: ByteArray,packetLen: Int){
            val profile = 2
            val freqIdx = 4
            val chanCfg = 2
            packet[0] = 0xff.toByte()
            packet[1] = 0xf9.toByte()
            packet[2] = ((profile-1).shl(6) + freqIdx.shl(2) + chanCfg.shr(2)).toByte()
            packet[3] = (chanCfg.and(3).shl(6) + packetLen.shr(11)).toByte()
            packet[4] = (packetLen.and(0x7ff).shr(3)).toByte()
            packet[5] = (packetLen.and(7).shl(5)+0x1f).toByte()
            packet[6] = 0xfc.toByte()
        }

        /**
         * aac解码并播放
         * 通过AudioTrack播放
         */
        fun aac2PCMAndPlay(context: Context,aacPath: String,outFile: File,decodeSuccess: () -> Unit,decodeFail: () -> Unit){
            thread {
                val audioExtractor = MediaExtractor()
                var decodeCodec: MediaCodec? = null
                var fos: FileOutputStream? = null
                var audioFormat: MediaFormat? = null
                try {
                    fos = FileOutputStream(outFile)
                    audioExtractor.setDataSource(aacPath)
                    val count = audioExtractor.trackCount
                    for (i in 0 until count){
                        audioFormat = audioExtractor.getTrackFormat(i)
                        if (audioFormat.getString(MediaFormat.KEY_MIME)?.startsWith("audio/") == true){
                            audioExtractor.selectTrack(i)
                            break
                        }
                    }
                    decodeCodec = MediaCodec.createDecoderByType(audioFormat?.getString(MediaFormat.KEY_MIME)?:"")
                    decodeCodec.configure(audioFormat,null,null,0)
                    val decodeBufferInfo = MediaCodec.BufferInfo()
                    decodeCodec.start()
                    var hasAudio = true
                    while (true){
                        /*
                            1、从Input缓冲区获取可用的InputBuffer编号
                            2、根据编号获取InputBuffer
                            3、往InputBuffer中装填数据
                            4、将装有数据的InoutBuffer放回Input缓冲区
                            5、读取下一帧数据
                        */
                        if (hasAudio){
                            //从Input缓冲区获取可用InputBuffer编号 -1一直等待 所以下面不用做编号为-1的处理
                            val inputIndex = decodeCodec.dequeueInputBuffer(-1)
                            //获取InputBuffer
                            val inputBuffer = decodeCodec.getInputBuffer(inputIndex)
                            //清空原有的旧数据
                            inputBuffer?.clear()
                            //将数据填到inputBuffer中
                            val readSize = audioExtractor.readSampleData(inputBuffer!!,0)
                            if (readSize == -1){
                                Log.e("TAG","当前音频已读取完")
                                hasAudio = false
                                //数据结束标志放到MediaCodeC的Input缓冲区去
                                decodeCodec.queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                            }else{
                                Log.e("TAG","此次读取到的音频数据长度：$readSize")
                                //将装填了数据的InputBuffer放回Input缓冲区
                                decodeCodec.queueInputBuffer(inputIndex,0,readSize,audioExtractor.sampleTime,0)
                                //AudioExtractor读取下一帧数据
                                audioExtractor.advance()
                            }
                        }
                        /*
                            这里需要转换一个概念，数据解码在MediaCodec内部已经完成，从Output缓冲区取出的OutputBuffer都是已经解码好的数据
                            1、从Output缓冲区取出已经解码好的OutputBuffer编号 传入的BufferInfo对象是去接收数据信息的，包含数据size以及数据flag等
                            2、根据OutputBuffer编号获取装填有数据并解码好的OutputBuffer
                            3、渲染或存储解码后的数据（处理）
                            4、将OutputBuffer放回到Output缓冲区
                         */
                        //获取Output缓冲区已经解码的OutputBuffer的编号  decodeBufferInfo->装填解码好的数据 第二个参数传-1时会一直卡住 传0前面的异常数据做处理不影响正常解码
                        val outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo,0)
                        Log.e("TAG","outputIndex:$outputIndex")
                        when(outputIndex){
                            //三个异常索引需要处理  不然下面根据索引获取OutputBuffer时抛异常
                            MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> Log.e("TAG","输出的format已更改：${decodeCodec.outputFormat}")
                            MediaCodec.INFO_TRY_AGAIN_LATER -> Log.e("TAG","超时未获取到")
                            MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> Log.e("TAG","输出的缓冲区已更改")
                            else ->{
                                Log.e("TAG","获取到解码后的数据，此次解码后的数据长度：${decodeBufferInfo.size}")
                                //获取装填有解码后数据的OutputBuffer
                                val outputBuffer = decodeCodec.getOutputBuffer(outputIndex)
                                //根绝解码后数据的大小创建ByteArray对象，后面往文件中写
                                val pcmData = ByteArray(decodeBufferInfo.size)
                                //将OutputBuffer中的数据装填到ByteArray中
                                outputBuffer?.get(pcmData)
                                outputBuffer?.clear()
                                fos.write(pcmData)
                                fos.flush()
                                //将OutputBuffer放回Output缓冲区中
                                decodeCodec.releaseOutputBuffer(outputIndex,false)
                            }
                        }
                        if (decodeBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0){
                            Log.e("TAG","解码完成")
                            decodeSuccess.invoke()
                            break
                        }
                    }
                }catch (e: Exception){
                    e.printStackTrace()
                    decodeFail.invoke()
                }finally {
                    audioExtractor.release()
                    decodeCodec?.stop()
                    decodeCodec?.release()
                    try {
                        fos?.close()
                    }catch (e: Exception){
                        e.printStackTrace()
                        decodeFail.invoke()
                    }
                }
            }
        }

        /**
         * 寻找帧头
         * @param data 需要寻找的数据
         * @param startIndex 需要寻找的起始位置
         * @param endIndex 需要寻找的结束位置
         * @return 帧头在data中位置   -1代表此段数据中没有帧头
         */
        private fun findHead(data: ByteArray,startIndex: Int,endIndex: Int): Int{
            for (i in startIndex .. endIndex){
                if (isHead(data,i)){
                    return i
                }
            }
            return -1
        }

        /**
         * 判断是否为帧头
         * 根据编码时每帧添加的ADTS头来判断
         */
        private fun isHead(data: ByteArray,offset: Int): Boolean{
            if (data[offset] == 0xff.toByte() && data[offset+1] == 0xf9.toByte() && data[offset+6] == 0xfc.toByte()){
                return true
            }
            return false
        }
    }
}